/*
 * Copyright 2013 Walter Lütgenau.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package offset.nodes.client.vdialog.model.navigation;

import java.applet.Applet;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import javax.jcr.PropertyType;
import netscape.javascript.JSObject;
import offset.nodes.Constants;
import offset.nodes.client.dialog.navigation.model.JSDocument;
import offset.nodes.client.dialog.navigation.model.JSElement;
import offset.nodes.client.dialog.navigation.model.JSNode;
import offset.nodes.client.dialog.navigation.model.NavigationModel;
import offset.nodes.client.dialog.navigation.model.NodeSelectionListener;
import offset.nodes.client.dialog.navigation.model.ServiceClient;
import offset.nodes.client.dialog.navigation.view.AbstractCardPanel;
import offset.nodes.client.vdialog.view.navigation.VirtualPagePanel;
import offset.nodes.client.model.NodeTypes;
import offset.nodes.client.model.Server;
import offset.nodes.client.model.SimpleNamespaceRegistry;
import offset.nodes.client.virtual.model.GetSchemas;
import offset.nodes.client.virtual.model.jcr.nodetype.NodeDefinition;
import offset.nodes.client.virtual.model.jcr.nodetype.NodeTypeDefinition;
import offset.nodes.client.virtual.model.jcr.nodetype.NodeTypeReader;
import offset.nodes.client.virtual.model.jcr.nodetype.PropertyDefinition;

/**
 *
 * @author Walter Lütgenau
 */
public class VirtualPageModel extends NavigationModel implements NodeSelectionListener, ServiceClient {

    public static final String ID_CONTENT = "content";
    Applet applet;
    protected VirtualPagePanel panel;
    NodeTypes nodeTypes;

    @Override
    public void initPanel() {
        this.panel = new VirtualPagePanel();
        this.panel.setNodeSelectionListener(this);
    }

    @Override
    public void setApplet(Applet applet) {
        this.applet = applet;
    }

    @Override
    public void setServer(Server server) {
        this.nodeTypes = getNodeTypes(server);
    }

    @Override
    public void invalidate() {
        super.invalidate();
        panel.start();
        panel.end();
    }

    /**
     * Update tree after displaying a new repository node
     */
    @Override
    public void updatePage() throws Exception {
        JSNode window = new JSNode((JSObject) JSObject.getWindow(applet));
        JSDocument document = new JSDocument(window.getNode("parent.contentFrame.document"));
        JSNode content = document.getElementById(ID_CONTENT);
        if (content == null)
            return;

        panel.start();
        addVirtualNodes(null, content);
        panel.end();
    }

    public AbstractCardPanel getPanel() {
        return panel;
    }

     @Override
    public void nodeSelected(String path) {
        JSNode node = (JSNode) panel.getSelectedUserObject();
        if (node != null) {
            JSNode window = new JSNode((JSObject) JSObject.getWindow(applet));
            window.getObject().call("highlight", new Object[] {node.getObject()});
            /*JSElement element = new JSElement(node);
            element.scrollIntoView();*/
        }

    }

    /**
     * Get the node types from the server.
     *
     * @param server the nodes message server
     * @return the node types
     */
    protected NodeTypes getNodeTypes(Server server) {
        GetSchemas.Request request = new GetSchemas.Request();
        GetSchemas.Response response = null;
        try {
            response = (GetSchemas.Response) server.sendRequest(request);
            InputStream in = new ByteArrayInputStream(response.getSchemaXml().getBytes());
            NodeTypeDefinition[] definitions = NodeTypeReader.read(in);

            NodeTypes types = new NodeTypes(definitions);

            return types;

        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    protected class AssociationInfo {

        String query;
        String nodeType;
        String name;
        int propertyType = -1;

        public String getNodeType() {
            return nodeType;
        }

        public void setNodeType(String nodeType) {
            this.nodeType = nodeType;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getPropertyType() {
            return propertyType;
        }

        public void setPropertyType(int propertyType) {
            this.propertyType = propertyType;
        }

        public String getQuery() {
            return query;
        }

        public void setQuery(String query) {
            this.query = query;
        }

        public String toString() {
            return "/" + getQuery() + "/" + getNodeType() + "/" + getPropertyType() + "/" + getName();
        }
    }
    
   /**
     * Add node associations for the node and/or subnodes.
     *
     * @param node the node
     */
    protected void addVirtualNodes(JSNode parent, JSNode node) {
        System.out.println(node.getName());
        if (node.getAttributes().isDefined(Constants.ATTRIBUTE_DATA)) {
            String value = (String) node.getAttributes().getAttribute(Constants.ATTRIBUTE_DATA);

            AssociationInfo info = parseDataAttribute(value);
            if (info == null)
                return; // something wrong. Ignore the branch
            
            System.out.println(info);
            if (info.getPropertyType() < 0) {

                panel.addNode(parent, node, info.getQuery(), info.getNodeType(), info.getName());
                if (info.getNodeType().equals(Constants.TYPE_HTML_DIV))
                    return; // do not parse sub elements
            } else {
                panel.addProperty(parent, node, info.getQuery(), info.getPropertyType(), info.getName());
                return; // do not parse sub elements
            }

            parent = node; // found n-data parent
        }

        if (!JSElement.isBlock(node.getName()))
            return;

        for (int i = 0; i < node.getElementCount(); i++) {
            if (!((JSNode) node.getElement(i)).isText())
                addVirtualNodes(parent, (JSNode) node.getElement(i));
        }
    }    

    /**
     * Parse the data attribute.
     *
     * The value has the structure "nodeType name/[child node name]/*[@ property name]"
     * or "/secondary?/nodeType name/[child node name]/*[@ property name]
     *
     * @param value the value to be parsed
     * @return the info about the values' structure.
     */
    protected AssociationInfo parseDataAttribute(String value) {
        System.out.println(value);
        AssociationInfo result = new AssociationInfo();
        boolean isProperty = value.indexOf(Constants.QUERY_ATTRIBUTE_KEY) >= 0;
        boolean isSecondary = value.startsWith(Constants.DATA_SECONDARY_QUERY);
        
        if (isSecondary) {
            int start = value.indexOf(Constants.PATH_SEPARATOR, 1) + 1;
            result.setQuery(value.substring(0, start));
            value = value.substring(start);
        }
        
        System.out.println(value);
        
        String[] components = value.split(Constants.PATH_SEPARATOR); // the path components

        if (components.length < 1)
            return null; // something wrong here

        NodeTypeDefinition nodeType = nodeTypes.get(SimpleNamespaceRegistry.getInstance().toQName(components[0]));
        if (nodeType == null)
            return null;

        int length = components.length;
        if (isProperty)
            length--;

        // starting from the first node type, determine the last node's type using the child names
        for (int i = 1; i < length; i++) {
            if (components[i].length() == 0) // should not happen, but you never know
                continue;

            for (NodeDefinition childNodeDefinition : nodeType.getChildNodeDefinitions()) {
                String prefixName = SimpleNamespaceRegistry.getInstance().toPrefixName(childNodeDefinition.getName());

                if (components[i].equals(prefixName)) {
                    nodeType = nodeTypes.get(childNodeDefinition.getDefaultPrimaryType());
                    break;
                }
            }
        }

        // set node type
        result.setNodeType(SimpleNamespaceRegistry.getInstance().toPrefixName(nodeType.getName()));

        // set name
        if (!isProperty)
            result.setName(components[components.length - 1]);
        else {
            // in case of property, set property type
            result.setName(components[components.length - 1].substring(1));

            if (result.getName().equals("NAME"))
                result.setPropertyType(PropertyType.STRING);
            else
                for (PropertyDefinition propertyDefinition : nodeType.getPropertyDefinitions()) {
                    String prefixName = SimpleNamespaceRegistry.getInstance().toPrefixName(propertyDefinition.getName());
                    if (result.getName().equals(prefixName)) {
                        result.setPropertyType(propertyDefinition.getRequiredType());
                        break;
                    }
                }

            if (result.getPropertyType() < 0)
                result.setPropertyType(PropertyType.UNDEFINED);
        }

        if (result.getName().indexOf(Constants.NAMESPACE_DELIMITER) >= 0) {
            String name[] = result.getName().split(Constants.NAMESPACE_DELIMITER);
            result.setName(name[1]);
        }

        return result;
    }
}
